package com.investmenttechnologyservices.model;

import java.util.ArrayList;
import java.util.Currency;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.time.DateUtils;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.investmenttechnologyservices.util.DateUtil;

public class Trade extends EntityHolder {
	//private Long key; //technical
	//Parent private Key accountKey; 
	//private String userId; 
	//private Long tradeId;
	//private StageType stageType;
	//private PositionType positionType
	//private Key symbolKey;
	//private Key blockTradeKey;
	//private Key instrumentTradeKey;
	//private String tradeDate; 
	//private String settlementDate; 
	//private Key brokerKey;
	//private Key counterpartyKey;
	//private Key cashAccountKey;
	//private Currency settlementCurrency; 
	//private List<String> tags;
	
	//private OpenClose openClose; get only 
	/**
	 * quantityClose - get only
	 * Calculated by the system based on trade and prior position.
	 * If the trade only opens a new position (long or short) then the value is zero.
	 * If the trade only opens or only closes then the value is zero. 
	 * If the trade closes and opens a long or short position then the value is set.
	 */
	//private Double quantityClose = 0.0;
	//private Double quantityPrevious = 0.0;
	
	//@Unindexed @NotSaved(IfDefault.class) private Double decimalPrice = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double quotePrice = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double accuredInterestAmount = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double principalAmount = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double netAmount = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double commissionAmount = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double taxAmount = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double feesAmount = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double fxRateInstPf = 0.0;
	//@Unindexed @NotSaved(IfDefault.class) private Double fxRateInstSett = 0.0;

	/**
	 * creationTimestamp: time stamp when record was created
	 * effectiveTimestamp: time stamp when record is effective. i.e. Trade initially done on 1/1/2012 11:00 UTC will
	 * 	show creation and effective of 1/1/2012 11:00 UTC. When this trade is corrected the creationTimestamp will
	 * 	be updated but the effectiveTimestamp will remain unchanged.
	 * expirationTimestamp: time when this record is no longer considered the most current version - i.e. corrected.
	 * 
	 * Corrected positions as of date X:
	 * 		expirationTimestamp != null
	 * Reported positions as of date X:
	 * 		effectiveTimestamp < X &&
	 * 		expirationTimestamp > X 
	 */
	//private DateTime creationTimestamp = new DateTime();
	//private DateTime effectiveTimestamp = new DateTime();
	//private DateTime expirationTimestamp = null;
	 	
	public Trade(Entity entity) {
		super(entity);
	}
	
	public Trade(Key accountKey) {
	    super("trade", accountKey);
	}

	public Trade(Long tradeId, String userId, StageType stageType, Key symbolKey, Key accountKey,
			Key blockTradeKey, Key instrumentTradeKey, Date tradeDate, Date settlementDate, Key brokerKey,
			Key counterpartyKey, Key cashAccountKey, Currency settlementCurrency,
			PositionType positionType, Set<String> tags, Double quantity, Double quotePrice, 
			Double accuredInterestAmount, Double commissionAmount, Double taxAmount, Double feesAmount,
			Double fxRateInstPf, Double fxRateInstSett, Date effectiveTimestamp, 
			Boolean initializePosition) {
		this(accountKey);
		setTradeId(tradeId);
		setUserId(userId);
		setStageType(stageType);
		setSymbolKey(symbolKey);
		setBlockTradeKey(blockTradeKey);
		setInstrumentTradeKey(instrumentTradeKey);
		setPositionType(positionType);
		setQuantity(quantity);
		setTradeDate(tradeDate);
		setSettlementDate(settlementDate);
		setBrokerKey(brokerKey);
		setCounterpartyKey(counterpartyKey);
		setCashAccountKey(cashAccountKey);
		setSettlementCurrency(settlementCurrency); 
		setTags(tags);
		setQuotePrice(quotePrice);
		setAccuredInterestAmount(accuredInterestAmount);
		setCommissionAmount(commissionAmount);
		setTaxAmount(taxAmount);
		setFeesAmount(feesAmount);
		setFxRateInstPf(fxRateInstPf);
		setFxRateInstSett(fxRateInstSett); 
		
		setQuantityPrevious(0.0);

		setCreationTimestamp(null);
		setEffectiveTimestamp(effectiveTimestamp);
		setExpirationTimestamp(null);
	}

	public Trade(String userId, StageType stageType, Key symbolKey, Key accountKey, Date tradeDate, 
			Set<String> tags, Double quantity, Double quotePrice,	Boolean initializePosition) {
		this(null, userId, stageType, symbolKey, accountKey, null, null, tradeDate, null,
				null, null, null, null, PositionType.HELD, tags, quantity, quotePrice, 0.0, 0.0, 
				0.0, 0.0, 0.0, 0.0, null, initializePosition);
	}

	public Trade(String userId, StageType stageType, Key symbolKey, Key accountKey, Date tradeDate, Date settlementDate,
			Key instrumentTradeKey, Set<String> tags, Double quantity, Boolean initializePosition) {
		this(null, userId, stageType, symbolKey, accountKey, null, instrumentTradeKey, tradeDate, settlementDate,
				null, null, null, null, PositionType.HELD, tags, quantity, 1.0, 0.0, 0.0, 
				0.0, 0.0, 0.0, 0.0, null, initializePosition);
	}

	/**
	 * The parent of each trade is the instrument account in which the trade is recorded. This method retrieves
	 * the parent key which is the key to the account from the trade's key.
	 * 
	 * @return parentKey
	 */
	
	public Key getKey() {return super.getKey();}
	public Key getParentKey() {return super.getParentKey();}
	
	public void setTradeId(Long tradeId) {
		setProperty("tradeId",tradeId);
	}
	public Long getTradeId() {return (Long)getProperty("tradeId");}
	
	public void setUserId(String userId) {setProperty("userId", userId);}
	public String getUserId() {return (String)getProperty("userId");}
	/**
	 * Set the Stage Type in the Trade object. Stage Type is a enumerated value whose name is stored.
	 * 
	 * @param stageType
	 */
	public void setStageType(StageType stageType) {
		if (stageType == null) {
			setProperty("stageType",StageType.TRANSACTION.name());
		} else {
			setProperty("stageType",stageType.name());
		}
	}
	/**
	 * Get the Stage Type in the Trade object. The name of the enum is stored and the enum is returned.
	 * 
	 * @param stageType
	 */
	public StageType getStageType() {
		StageType stageType = StageType.valueOf((String) getProperty("stageType") );
		return stageType;
	}

	public void setVersion(Long version) {setProperty("version",version);}
	public Long getVersion() {
		Long version = (Long)getProperty("version");
		if (version == null) {
			version = 0L;
		}
		return version;
	}
	/**
	 * Set the tradeActive flag (true state indicates the trade should be aggregated to determine
	 * 	position). Only stored if false.
	 * 
	 * Deprecated and replaced by expirationTimestamp. 
	 *  
	 * @param tradeActive
	 
	public void setTradeActive(Boolean tradeActive) {
		if (tradeActive != null && tradeActive == false) {
			setProperty("tradeActive",tradeActive);
		}
	}
	public Boolean getTradeActive() {
		Boolean tradeActive = (Boolean) getProperty("tradeActive");
		if (tradeActive == null) {
			tradeActive = true;
		}
		return tradeActive;
	}
*/	
	//symbolKey
	public void setSymbolKey(Key symbolKey) {setProperty("symbolKey",symbolKey);}
	public Key getSymbolKey() {return (Key)getProperty("symbolKey");}

	//BlockTradeKey
	public void setBlockTradeKey(Key blockTradeKey){setProperty("blockTradeKey",blockTradeKey);}
	public Key getBlockTradeKey() {return (Key)getProperty("blockTradeKey");}
	
	//tradeDate
	public void setTradeDate(Date tradeDate) {
		if (tradeDate != null)
			setProperty("tradeDate",tradeDate);
	}
	
	public Date getTradeDate() {
		return (Date) getProperty("tradeDate");
	}
	//SettlementDate
	public void setSettlementDate(Date settlementDate) {
		if (settlementDate != null)
			setUnindexedProperty("settlementDate",settlementDate);
	}
	
	public Date getSettlementDate() {
		return (Date) getProperty("settlementDate");
	}

	//brokerKey
	public void setBrokerKey(Key brokerKey) {
		setProperty("brokerKey",brokerKey);
	}
	
	public Key getBrokerKey() {
		return (Key)getProperty("brokerKey");
	}

	//counterpartyKey
	public void setCounterpartyKey(Key counterpartyKey) {
		setUnindexedProperty("counterpartyKey",counterpartyKey);
	}
	
	public Key getCounterpartyKey() {
		return (Key)getProperty("counterpartyKey");
	}
	//cashAccountKey
	public void setCashAccountKey(Key cashAccountKey) {
		setUnindexedProperty("cashAccountKey",cashAccountKey);
	}
	
	public Key getCashAccountKey() {
		return (Key) getProperty("cashAccountKey");
	}
	public void setInstrumentTradeKey(Key instrumentTradeKey) {
		setProperty("instrumentTradeKey",instrumentTradeKey);
	}
	public Key getInstrumentTradeKey() {
		return (Key) getProperty("instrumentTradeKey");
	}
	//settlementCurrency
	public void setSettlementCurrency(Currency settlementCurrency) {
		setUnindexedProperty("settlementCurrency",settlementCurrency);
	}
	
	public Currency getSettlementCurrency() {
		return (Currency) getProperty("settlementCurrency");
	}
	//Position Type
	public void setPositionType(PositionType positionType) {
		if (positionType == null) {
			// needs to be coded
			setProperty("positionType",PositionType.HELD.name());
		} else {
			setProperty("positionType",positionType.name());
		}
	}
	public PositionType getPositionType() {
		String enumName = (String) getProperty("positionType");
		if (enumName != null) {
			PositionType positionType = PositionType.valueOf(enumName);
			return positionType;
		}
		return null;
	}
	// tags
	public void setTags(Set<String> tags) {
		if (tags != null) {
			setProperty("tags",new ArrayList<String>(tags));
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<String> getTags() {
		return (List<String>) getProperty("tags");
	}
	//quantity
	public void setQuantity(Double quantity) {
		setUnindexedProperty("quantity",quantity);
	}
	
	public Double getQuantity() {
		return (Double)getProperty("quantity");
	}
	// quantityNextStage
	public void setQuantityPrevious(Double quantityPrevious) {
		setPropertyAsNullIfZero("quantityPrevious",quantityPrevious);
	}
	
	public Double getQuantityPrevious() {
		return getPropertyAsZeroIfNull("quantityPrevious");
	}
	// decimalPrice
	public void setDecimalPrice(Double decimalPrice) {
		setUnindexedProperty("decimalPrice",decimalPrice);
	}
	
	public Double getDecimalPrice() {
		return (Double)getProperty("decimalPrice");
	}
	// quotePrice
	public void setQuotePrice(Double quotePrice) {
		setUnindexedProperty("quotePrice",quotePrice);
	}
	
	public Double getQuotePrice() {
		return (Double)getProperty("quotePrice");
	}
	// accuredInterestAmount
	public void setAccuredInterestAmount(Double accuredInterestAmount) {
		setUnindexedProperty("accuredInterestAmount",accuredInterestAmount, true);
	}
	
	public Double getAccuredInterestAmount() {
		return getPropertyAsZeroIfNull("accuredInterestAmount");	
	}
	// principalAmount
	public void setPrincipalAmount(Double principalAmount) {
		setUnindexedProperty("principalAmount",principalAmount);
	}
	
	public Double getPrincipalAmount() {
		return getPropertyAsZeroIfNull("principalAmount");	
	}
	// netAmount
	public void setNetAmount(Double netAmount) {
		setUnindexedProperty("netAmount",netAmount);
	}
	
	public Double getNetAmount() {
		return getPropertyAsZeroIfNull("netAmount");	
	}
	
	// commissionAmount 
	public void setCommissionAmount(Double commissionAmount) {
		setUnindexedProperty("commissionAmount",commissionAmount, true);
	}
	
	public Double getCommissionAmount() {
		return getPropertyAsZeroIfNull("commissionAmount");	
	}
	// taxAmount
	public void setTaxAmount(Double taxAmount) {
		setUnindexedProperty("taxAmount",taxAmount, true);
	}
	
	public Double getTaxAmount() {
		return getPropertyAsZeroIfNull("taxAmount");	
	}
	// feesAmount
	public void setFeesAmount(Double feesAmount) {
		setUnindexedProperty("feesAmount",feesAmount, true);
	}
	
	public Double getFeesAmount() {
		return getPropertyAsZeroIfNull("feesAmount");
	}
	// fxRateInstPf
	public void setFxRateInstPf(Double fxRateInstPf) {
		setUnindexedProperty("fxRateInstPf",fxRateInstPf, true);
	}
	
	public Double getFxRateInstPf() {
		return getPropertyAsZeroIfNull("fxRateInstPf");
	}
	// fxRateInstSett
	public void setFxRateInstSett(Double fxRateInstSett) {
		setUnindexedProperty("fxRateInstSett",fxRateInstSett, true);
	}
	
	public Double getFxRateInstSett() {
		return getPropertyAsZeroIfNull("fxRateInstSett");
	}

	public Date getCreationTimestamp() {return (Date) getProperty("creationTimestamp");}
	public void setCreationTimestamp() {
		setProperty("creationTimestamp",DateUtil.dateTimeNowUTC());
	}
	public void setCreationTimestamp(Date creationTimestamp) {
		if (creationTimestamp == null) {
			creationTimestamp = DateUtil.dateTimeNowUTC();
		}
		setProperty("creationTimestamp",creationTimestamp);
	}
	public Date getEffectiveTimestamp() {return (Date) getProperty("effectiveTimestamp");}
	public void setEffectiveTimestamp() {
		Date tradeDate = getTradeDate();
		if (tradeDate==null || DateUtils.isSameDay(tradeDate, new Date())) {
			setProperty("effectiveTimestamp",DateUtil.dateTimeNowUTC());
		} else {
			setProperty("effectiveTimestamp",DateUtil.createEODUTC(tradeDate));
		}
	}
	public void setEffectiveTimestamp(Date effectiveTimestamp) {
		if (effectiveTimestamp == null) {
			Date tradeDate = getTradeDate();
			if (tradeDate==null || DateUtils.isSameDay(tradeDate, new Date())) {
				effectiveTimestamp = DateUtil.dateTimeNowUTC();
			} else {
				effectiveTimestamp = DateUtil.createEODUTC(tradeDate);
			}
		}
		setProperty("effectiveTimestamp",effectiveTimestamp);
	}

	public Date getExpirationTimestamp() {return (Date) getProperty("expirationTimestamp");}
	public void setExpirationTimestamp() {
		setProperty("expirationTimestamp",DateUtil.dateTimeNowUTC());
	}
	public void setExpirationTimestamp(Date expirationTimestamp) {
		setProperty("expirationTimestamp",expirationTimestamp);
	}

	public OpenClose getOpenClose() {
		OpenClose openClose = OpenClose.OPEN;
		if ((getQuantity()<0 && getQuantityPrevious()>0) ||
			(getQuantity()>0 && getQuantityPrevious()<0)) {
			openClose = OpenClose.CLOSE;
		}
		return openClose;
	}
	
	public Double getQuantityClose() {
		Double quantityClose = 0.0;
		Double tradeQuantity = getQuantity();
		Double positionQuantity = getQuantityPrevious();
		
		if ((tradeQuantity<0 && positionQuantity>0) ||
			(tradeQuantity>0 && positionQuantity<0)) {
			if (Math.abs(tradeQuantity) <= Math.abs(positionQuantity)) {
				quantityClose = tradeQuantity;
			} else {
				quantityClose = positionQuantity;
			}
		} 
		return quantityClose;
	}

	@Override
	public String toString() {
		return "Trade [Key="+getKey()+"\tTradeId=" + getTradeId() + 
				", \tAccount="+getParentKey() +
				", \tSymbol="+getSymbolKey().toString()+
				", \tstageType=" + getStageType() + ", \tquantity=" + getQuantity() + 
				", \tQty Prev=" + getQuantityPrevious() + ", \tQuote Price=" + getQuotePrice() + 
				", \tPrinc=" + getPrincipalAmount().toString()+
				", \tExp=" + getExpirationTimestamp() + ", Create="+getCreationTimestamp() + 
				", \tEff=" + getEffectiveTimestamp() +
				((getInstrumentTradeKey()==null)?"":", Instrument Trade="+getInstrumentTradeKey().toString())+"]";
	}

}
